﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;

namespace XLang
{
    /// <summary>
    /// 词法分析器
    /// </summary>
    public class Lexer
    {
        #region 数据属性
        /// <summary>
        /// 关键字表
        /// </summary>
        public List<KeywordRule> Keywords { get; private set; }

        /// <summary>
        /// 标点符号表
        /// </summary>
        public List<PunctuationRule> Punctuations { get; private set; }

        /// <summary>
        /// Token表
        /// </summary>
        public List<TokenRule> Tokens { get; private set; }
        #endregion

        #region 构造函数
        public Lexer(Assembly assembly)
        {
            this.Keywords = new List<KeywordRule>();
            this.Punctuations = new List<PunctuationRule>();
            this.Tokens = new List<TokenRule>();

            Init(assembly);
        }

        protected virtual void Init(Assembly assembly)
        {
            var keywordType = typeof(Keyword);
            var punctuationType = typeof(Punctuation);
            var tokenType = typeof(Token);

            var types = assembly.GetTypes();

            foreach (var type in types)
            {
                if (type.IsAbstract) continue;

                if (type.IsInherit(keywordType))
                {
                    this.Keywords.Add(new KeywordRule(type));
                }
                else if (type.IsInherit(punctuationType))
                {
                    this.Punctuations.Add(new PunctuationRule(type));
                }
                else if (type.IsInherit(tokenType))
                {
                    this.Tokens.Add(new TokenRule(type));
                }
            }
            Check();
        }

        #region Check
        protected virtual void Check()
        {
            var keywordList = Keywords.Select(i => i.Keyword).ToList();
            if (keywordList.Count != keywordList.Distinct().Count()) throw new Exception("keyword must be unique.");

            var punctuationList = Punctuations.Select(i => i.Punctuation).ToList();
            if (punctuationList.Count != punctuationList.Distinct().Count()) throw new Exception("punctuation must be unique.");
        }
        #endregion
        #endregion

        #region ReadToken
        public Token ReadToken(LContext context)
        {
            var token = GetToken(context);
            if (token == null) return token;

            while (token.GetType().IsDefined(typeof(IgnoreAttribute))) {
                token = GetToken(context);
                if (token == null) return token;
            }
            return token;
        }
        protected virtual Token GetToken(LContext context)
        {
            if (context.Position > context.Source.Length) return null;

            var currentState = context.CurrentState;
            var pos = context.Position;

            foreach (var rule in Tokens)
            {
                if (!rule.State.Contains(currentState)) continue;

                var tmp = rule.Create(context);
                if (tmp == null)
                {
                    context.SetPosition(pos - 1);
                    context.GetChar();
                    continue;
                }

                if (tmp.GetType() == typeof(string))
                {
                    var tokenText = (string)tmp;

                    var keywordRule = Keywords.FirstOrDefault(i => i.Keyword == tokenText && i.State.Contains(currentState));
                    if (keywordRule != null)
                    {
                        var keywordToken = keywordRule.Create();
                        keywordToken.Text = tokenText;
                        keywordToken.Position = pos;
                        return keywordToken;
                    }

                    var punctuationRule = Punctuations.FirstOrDefault(i => i.Punctuation == tokenText && i.State.Contains(currentState));
                    if (punctuationRule != null)
                    {
                        var punctuationToken = punctuationRule.Create();
                        punctuationToken.Text = tokenText;
                        punctuationToken.Position = pos;
                        return punctuationToken;
                    }

                    var token = (Token)Activator.CreateInstance(rule.Type);
                    token.Text = tokenText;
                    token.Position = pos;
                    return token;
                }

                var result = (Token)tmp;
                result.Position = pos;
                return result;
            }

            var ptoken = ReadPunctuation(context);
            if (ptoken != null)
            {
                return ptoken;
            }

            throw new Exception("unknow token,postion:" + pos);
        }
        #endregion

        #region ReadPunctuation
        protected virtual Token ReadPunctuation(LContext context)
        {
            var ls = Punctuations.Where(i => i.State.Contains(context.CurrentState)).Select(i => i.Punctuation).OrderByDescending(i => i.Length).ToList();
            var group = new Dictionary<int, List<string>>();
            foreach (var item in ls)
            {
                var key = item.Length;
                if (!group.ContainsKey(key)) group.Add(key, new List<string>());
                group[key].Add(item);
            }

            foreach (var g in group)
            {
                if (context.Position + g.Key > context.Source.Length) continue;
                var subString = context.Source.Substring(context.Position, g.Key);
                foreach (var tokenText in g.Value)
                {
                    if (tokenText == subString)
                    {
                        var punctuationRule = Punctuations.FirstOrDefault(i => i.Punctuation == tokenText && i.State.Contains(context.CurrentState));
                        if (punctuationRule != null)
                        {
                            var punctuationToken = punctuationRule.Create();
                            punctuationToken.Text = tokenText;
                            punctuationToken.Position = context.Position;
                            context.SetPosition(context.Position + tokenText.Length);
                            return punctuationToken;
                        }
                    }
                }
            }

            return null;
        }
        #endregion


    }
}
